﻿//---------------------------------------------------------------------------
//
// Copyright (C) Inter Access B.V..  All rights reserved.
//
//---------------------------------------------------------------------------

using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.Generic;

namespace Schedule.Primitives
{
    /// <summary>
    /// Panel that displays TimeItems. 
    /// TimeItems that intersect with the TimePanel get displayed
    /// </summary>
    public class TimePanel : Panel
    {
        static TimePanel()
        {
            
        }

        public TimePanel()
        {
            
        }

        #region Dependency Properties

        /// <summary>
        /// DateTime to present from
        /// </summary>
        public DateTime Start
        {
            get { return (DateTime)GetValue(StartProperty); }
            set { SetValue(StartProperty, value); }
        }

        public static readonly DependencyProperty StartProperty =
            DependencyProperty.Register(
                "Start",
                typeof(DateTime),
                typeof(TimePanel),
                new FrameworkPropertyMetadata()
                {
                    AffectsMeasure = true,
                    AffectsArrange = true
                });

        /// <summary>
        /// DateTime to present to
        /// </summary>
        public DateTime End
        {
            get { return (DateTime)GetValue(EndProperty); }
            set { SetValue(EndProperty, value); }
        }

        public static readonly DependencyProperty EndProperty =
            DependencyProperty.Register(
                "End", 
                typeof(DateTime), 
                typeof(TimePanel), 
                new FrameworkPropertyMetadata()
                {
                    AffectsMeasure = true,
                    AffectsArrange = true
                });

        /// <summary>
        /// Amount of colums displayed.
        /// This is a helper property for aligning the TimeItems
        /// </summary>
        public int Columns
        {
            get { return (int)GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }

        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register(
                "Columns",
                typeof(int),
                typeof(TimePanel),
                new FrameworkPropertyMetadata(1)
                {
                    AffectsArrange = true
                });

        /// <summary>
        /// The minimum height of the selected ItemSize, used with vertical orientation
        /// </summary>
        public double MinimumTimePeriodHeight
        {
            get { return (double)GetValue(MinimumTimePeriodHeightProperty); }
            set { SetValue(MinimumTimePeriodHeightProperty, value); }
        }

        public static readonly DependencyProperty MinimumTimePeriodHeightProperty =
            DependencyProperty.Register("MinimumTimePeriodHeight", typeof(double), typeof(TimePanel), new UIPropertyMetadata(50d));

        /// <summary>
        /// The minimum height of the selected ItemSize, used with horizontal orientation
        /// </summary>
        public double MinimumTimePeriodWidth
        {
            get { return (double)GetValue(MinimumTimePeriodWidthProperty); }
            set { SetValue(MinimumTimePeriodWidthProperty, value); }
        }

        public static readonly DependencyProperty MinimumTimePeriodWidthProperty =
            DependencyProperty.Register("MinimumTimePeriodWidth", typeof(double), typeof(TimePanel), new UIPropertyMetadata(50d));

        /// <summary>
        /// The orientation of the TimePanel, only Vertical is implented at this time.
        /// </summary>
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(TimePanel),
            new FrameworkPropertyMetadata(Orientation.Vertical)
            {
                AffectsArrange = true,
                AffectsMeasure = true
            });

        /// <summary>
        /// The granuality of the TimePanel, the large the timespan the smaller the TimePanel.
        /// </summary>
        public TimeItemSize ItemSize
        {
            get { return (TimeItemSize)GetValue(ItemSizeProperty); }
            set { SetValue(ItemSizeProperty, value); }
        }

        public static readonly DependencyProperty ItemSizeProperty =
            DependencyProperty.Register("ItemSize", typeof(TimeItemSize), typeof(TimePanel),
            new FrameworkPropertyMetadata(OnItemSizeChanged)
            {
                AffectsMeasure = true,
                AffectsArrange = true
            });

        private static void OnItemSizeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            TimePanel panel = (TimePanel)sender;
            // Communicate to listeners that the itemsize has changed
            panel.RaiseItemSizeChanged();
        }

        public static readonly RoutedEvent ItemSizeChangedRoutedEvent = EventManager.RegisterRoutedEvent(
            "ItemSizeChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler),typeof(TimePanel));

        /// <summary>
        /// Event that is raised when the ItemSize has changed
        /// </summary>
        public event RoutedEventHandler ItemSizeChanged
        {
            add { AddHandler(ItemSizeChangedRoutedEvent, value); }
            remove { RemoveHandler(ItemSizeChangedRoutedEvent, value); }
        }

        private void RaiseItemSizeChanged()
        {
            RoutedEventArgs newEventArgs = new RoutedEventArgs(TimePanel.ItemSizeChangedRoutedEvent);
            RaiseEvent(newEventArgs);
        }

        #endregion

        #region Attached Properties

        /// <summary>
        /// Returns the DateTime for the ItemStart property of the item
        /// </summary>
        /// <param name="obj">Item to get start DateTime from</param>
        /// <returns>Start DateTime from item, 1-1-0001 if empty</returns>
        public static DateTime GetItemStart(DependencyObject obj)
        {
            return (DateTime)obj.GetValue(ItemStartProperty);
        }

        /// <summary>
        /// Sets the DateTime for the ItemStart property
        /// </summary>
        /// <param name="obj">Item to set the start DateTime from</param>
        /// <param name="value">DateTime to set the item to</param>
        public static void SetItemStart(DependencyObject obj, DateTime value)
        {
            obj.SetValue(ItemStartProperty, value);
        }

        /// <summary>
        /// Attached Property of the start DateTime of the TimeItem
        /// </summary>
        public static readonly DependencyProperty ItemStartProperty =
            DependencyProperty.RegisterAttached(
                "ItemStart",
                typeof(DateTime),
                typeof(TimePanel),
                new FrameworkPropertyMetadata(OnItemStartChanged, OnItemStartCoerce)
                {
                    AffectsArrange = true,
                    AffectsMeasure = true
                });


        /// <summary>
        /// Returns the DateTime for the ItemEnd property of the item
        /// </summary>
        /// <param name="obj">Item to get end DateTime from</param>
        /// <returns>End DateTime from item, 1-1-0001 if empty</returns>
        public static DateTime GetItemEnd(DependencyObject obj)
        {
            return (DateTime)obj.GetValue(ItemEndProperty);
        }

        /// <summary>
        /// Sets the DateTime for the ItemEnd property
        /// </summary>
        /// <param name="obj">Item to set the end DateTime from</param>
        /// <param name="value">DateTime to set the item to</param>
        public static void SetItemEnd(DependencyObject obj, DateTime value)
        {
            obj.SetValue(ItemEndProperty, value);
        }

        /// <summary>
        /// Attached property of the end DateTime of a TimeItem
        /// </summary>
        public static readonly DependencyProperty ItemEndProperty =
            DependencyProperty.RegisterAttached(
                "ItemEnd",
                typeof(DateTime),
                typeof(TimePanel),
                new FrameworkPropertyMetadata(OnItemEndChanged, OnItemEndCoerce)
                {
                    AffectsArrange = true,
                    AffectsMeasure = true
                });

        /// <summary>
        /// Gets the columnspan of an TimeItem in the panel.
        /// </summary>
        /// <param name="obj">Item to get the columnspan from</param>
        /// <returns>Amount of columns the TimeItem takes up</returns>
        public static int GetColumnSpan(DependencyObject obj)
        {
            return (int)obj.GetValue(ColumnSpanProperty);
        }

        /// <summary>
        /// Sets the columnspan of an TimeItem in the panel.
        /// </summary>
        /// <param name="obj">Item to set the columnspan of</param>
        /// <param name="value">Amount of columns to take up</param>
        public static void SetColumnSpan(DependencyObject obj, int value)
        {
            obj.SetValue(ColumnSpanProperty, value);
        }

        /// <summary>
        /// Helper property for deciding the width of the TimeItem
        /// </summary>
        public static readonly DependencyProperty ColumnSpanProperty =
            DependencyProperty.RegisterAttached(
                "ColumnSpan",
                typeof(int),
                typeof(TimePanel),
                new FrameworkPropertyMetadata(1, OnColumnSpanChanged)
                {
                    AffectsArrange = true,
                    AffectsMeasure = true
                });

        /// <summary>
        /// Gets the column number wich the item is displayed in
        /// </summary>
        /// <param name="obj">item to get the column number from</param>
        /// <returns>The column number of the item</returns>
        public static int GetColumn(DependencyObject obj)
        {
            return (int)obj.GetValue(ColumnProperty);
        }

        /// <summary>
        /// Sets the column number of an item
        /// </summary>
        /// <param name="obj">item to set the column number of</param>
        /// <param name="value">The column number to place the item on</param>
        public static void SetColumn(DependencyObject obj, int value)
        {
            obj.SetValue(ColumnProperty, value);
        }

        /// <summary>
        /// Attached property to decide where to place the item
        /// This is a helper property for layout
        /// </summary>
        public static readonly DependencyProperty ColumnProperty =
            DependencyProperty.RegisterAttached(
                "Column",
                typeof(int),
                typeof(TimePanel),
                new FrameworkPropertyMetadata(0, OnColumnPropertyChanged)
                {
                    AffectsArrange = true,
                    AffectsMeasure = true
                }
                );

        #endregion

        private static void OnItemStartChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TimeItem item = (TimeItem)d;
            if (item != null && item.Parent != null)
            {
                string blaat = "";
            }
        }

        private static void OnItemEndChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            
        }

        private static object OnItemStartCoerce(DependencyObject d, object baseValue)
        {
            TimeItem typedItem = (TimeItem)d;
            
            return baseValue;
        }

        private static object OnItemEndCoerce(DependencyObject d, object baseValue)
        {
            TimeItem typedItem = (TimeItem)d;

            return baseValue;
        }
        
        private static void OnColumnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

        }

        private static void OnColumnSpanChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

        }

        protected override Size MeasureOverride(Size availableSize)
        {
            if ((int)ItemSize == 0) return new Size(0, 0);

            Size actualSize = new Size();
            double unitsPerSecond = 0;
            double totalSeconds = (End - Start).TotalSeconds;

            switch (Orientation)
            {
                case Orientation.Horizontal:
                    actualSize = new Size(
                        (totalSeconds / (double)ItemSize) * MinimumTimePeriodWidth, availableSize.Height);
                    unitsPerSecond = actualSize.Width / (End - Start).TotalSeconds;
                    break;
                case Orientation.Vertical:
                    actualSize = new Size(
                        availableSize.Width, (totalSeconds / (double)ItemSize) * MinimumTimePeriodHeight);
                    unitsPerSecond = actualSize.Height / (End - Start).TotalSeconds;
                    break;
            }
            if (Double.IsInfinity(unitsPerSecond)) return actualSize;

            foreach(TimeItem child in this.Children)
            {
                DateTime start = TimePanel.GetItemStart(child);
                DateTime end = TimePanel.GetItemEnd(child);

                start = start < this.Start ? this.Start : start;
                end = end > this.End ? this.End : end;

                double height = unitsPerSecond * (end - start).TotalSeconds;
                double width = (availableSize.Width / Columns);
                if (Double.IsNaN(height)) height = 100;
                Size size = new Size();
                size.Height = height;
                size.Width = width;
                child.Measure(size);
            }
            
            return actualSize;
        }

        private void NotifyObserversItemNew(TimeItem newItem)
        {
            foreach (TimeItemObserver observer in _observers.Values)
            {
                // Notify observers, unless it's our own observer. That would be silly
                if (observer.Intersects(newItem) && observer.Item != newItem)
                {
                    observer.IntersectedItems.Add(newItem);
                }
            }
        }

        private void NotifyObserversItemChange(TimeItem changedItem)
        {
            foreach (TimeItemObserver observer in _observers.Values)
            {
                // Notify observers, unless it's our own observer.
                if (changedItem != observer.Item)
                {
                    if (observer.IntersectedItems.Contains(changedItem) && observer.Intersects(changedItem))
                    {
                        // If all we did is just grown a little
                        observer.ArrangeItemFirstPass();
                    }
                    else
                    {
                        // If we dont intersect anymore, remove
                        observer.IntersectedItems.Remove(changedItem);
                    }
                }
                else
                {
                    // Our item is changed, we need to make sure we still intersect with the items we are looking at
                }
            }
        }

        private void NotifyObserversItemRemove(TimeItem removedItem)
        {
            foreach (TimeItemObserver observer in _observers.Values)
            {
                if (observer.IntersectedItems.Contains(removedItem))
                {
                    observer.IntersectedItems.Remove(removedItem);
                }
            }
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if ((int)ItemSize == 0) return base.ArrangeOverride(finalSize);

            double unitsPerSecond = 0;

            switch (Orientation)
            {
                case Orientation.Horizontal:
                    unitsPerSecond = finalSize.Width / (End - Start).TotalSeconds;
                    break;
                case Orientation.Vertical:
                    unitsPerSecond = finalSize.Height / (End - Start).TotalSeconds;
                    break;
            }
            if (Double.IsInfinity(unitsPerSecond)) return finalSize;

            // Arrange the children
            foreach (TimeItem child in this.Children)
            {
                // Get the start and end properties
                DateTime start = TimePanel.GetItemStart(child);
                DateTime end = TimePanel.GetItemEnd(child);

                // If they are out of bounds, limit them to TimePanel start and end
                start = start < this.Start ? this.Start : start;
                end = end > this.End ? this.End : end;

                // Calculate height and width
                double height = unitsPerSecond * (end - start).TotalSeconds;
                double width = (finalSize.Width / Columns) * TimePanel.GetColumnSpan(child);

                // Calculate x and y
                double x = TimePanel.GetColumn(child) * (finalSize.Width / Columns);
                double y = unitsPerSecond * (start - Start).TotalSeconds;

                // Make a new Rect and arrange!
                Rect box = new Rect(x, y, width, height);
                child.Arrange(box);
            }
            return finalSize;
        }

        private Dictionary<TimeItem, TimeItemObserver> _observers = new Dictionary<TimeItem, TimeItemObserver>();

        protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
        {            
            base.OnVisualChildrenChanged(visualAdded, visualRemoved);
            
            if (visualAdded != null && visualAdded.GetType() == typeof(TimeItem))
            {
                TimeItem item = (TimeItem)visualAdded;
                if (!_observers.ContainsKey(item))
                {
                    TimeItemObserver observer = new TimeItemObserver(item, this);
                    _observers.Add(item, observer);
                    NotifyObserversItemNew(item);
                }
            }

            if (visualRemoved != null && visualRemoved.GetType() == typeof(TimeItem))
            {
                TimeItem item = (TimeItem)visualRemoved;
                if (_observers.ContainsKey(item))
                {
                    TimeItemObserver observer = _observers[item];
                    _observers.Remove(item);
                    observer = null;
                    NotifyObserversItemRemove(item);
                }

            }
        }
    }
}
